/*
 * output.h
 *
 *  Created on: Jan 6, 2012
 *      Author: liuhaifeng
 */

#ifndef _OUTPUT_H_
#define _OUTPUT_H_

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <defs.h>
#include <vars.h>
#include <dataset.h>
#include <http.h>

#define MAX_OUTPUT_PARAM_LEN 32

typedef struct st_format_token {
	int place_holder;
	char param[MAX_OUTPUT_PARAM_LEN];
} FormatToken;

static void _output_add_token(int place_holder, char *param, list *out_stream) {
	FormatToken *ft = (FormatToken *) awl_alloc(sizeof(FormatToken));
	ft->place_holder = place_holder;
	if (param != NULL)
		strncpy(ft->param, param, MAX_OUTPUT_PARAM_LEN);
	push(out_stream, ft);
}

static const char * _parse_time_holder(const char *str, list *out_stream) {
	switch (*str) {
	case 'y':
		_output_add_token(VAR_TIME_YYYY, NULL, out_stream);
		str++;
		break;
	case 'M':
		_output_add_token(VAR_TIME_MM, NULL, out_stream);
		str++;
		break;
	case 'd':
		_output_add_token(VAR_TIME_DD, NULL, out_stream);
		str++;
		break;
	case 'H':
		_output_add_token(VAR_TIME_HH, NULL, out_stream);
		str++;
		break;
	case 'm':
		_output_add_token(VAR_TIME_II, NULL, out_stream);
		str++;
		break;
	case 's':
		_output_add_token(VAR_TIME_SS, NULL, out_stream);
		str++;
		break;
	case 'z':
		str++;
		switch (*str) {
		case 'f':
			_output_add_token(VAR_TIME_ZONE_FLAG, NULL, out_stream);
			str++;
			break;
		case 'h':
			_output_add_token(VAR_TIME_ZONE_HH, NULL, out_stream);
			str++;
			break;
		case 'm':
			_output_add_token(VAR_TIME_ZONE_II, NULL, out_stream);
			str++;
			break;
		default:
			_output_add_token(VAR_TIME_ZONE, NULL, out_stream);
			break;
		}
		break;
	default:
		_output_add_token(VAR_TIME, NULL, out_stream);
		break;
	}
	return str;
}

static const char * _parse_url_holder(const char *str, list *out_stream) {
	switch (*str) {
	case 'p':
		_output_add_token(VAR_REQUEST_PATH, NULL, out_stream);
		str++;
		break;
	case 'a':
		_output_add_token(VAR_REQUEST_ANCHOR, NULL, out_stream);
		str++;
		break;
	default:
		_output_add_token(VAR_REQUEST_URL, NULL, out_stream);
		break;
	}
	return str;
}

static const char * _parse_request_param_holder(const char *str,
		list *out_stream) {
	FormatToken *ft = (FormatToken *) awl_alloc(sizeof(FormatToken));
	int i = 0;

	ft->place_holder = VAR_REQUEST_PARAM;

	while (str != NULL && *str != ')' && *str != 0
			&& i < MAX_OUTPUT_PARAM_LEN - 1) {
		ft->param[i++] = *str++;
	}

	if (i > 0) {
		ft->param[i] = 0;
		push(out_stream, ft);
	} else {
		awl_free(ft);
	}

	return str;
}

static const char * _parse_place_holder(const char *str, list *out_stream) {
	switch (*str) {
	case 'h':
		_output_add_token(VAR_HOST_ADDR, NULL, out_stream);
		str++;
		break;
	case 'i':
		_output_add_token(VAR_IDENTITY, NULL, out_stream);
		str++;
		break;
	case 'u':
		_output_add_token(VAR_USERID, NULL, out_stream);
		str++;
		break;
	case 't':
		str = _parse_time_holder(++str, out_stream);
		break;
	case 'm':
		_output_add_token(VAR_REQUEST_METHOD, NULL, out_stream);
		str++;
		break;
	case 'l':
		str = _parse_url_holder(++str, out_stream);
		break;
	case '(':
		str = _parse_request_param_holder(++str, out_stream);
		if (str != NULL && *str == ')')
			str++;
		break;
	case 'p':
		_output_add_token(VAR_REQUEST_PROTOCOL, NULL, out_stream);
		str++;
		break;
	case 's':
		_output_add_token(VAR_STATUS, NULL, out_stream);
		str++;
		break;
	case 'b':
		_output_add_token(VAR_BYTES, NULL, out_stream);
		str++;
		break;
	case 'r':
		_output_add_token(VAR_REFERER, NULL, out_stream);
		str++;
		break;
	case 'a':
		_output_add_token(VAR_USER_AGENT, NULL, out_stream);
		str++;
		break;
	default:
		fprintf(stderr,
				"Failed to parse output format, unrecognized place holder: %%%c.\n",
				*str);
		return NULL;
		break;
	}
	return str;
}

static const char * _parse_const_str(const char *str, list *out_stream) {
	FormatToken *ft = (FormatToken *) awl_alloc(sizeof(FormatToken));
	int i = 0;

	ft->place_holder = VAR_CONST_STR;

	while (str != NULL && *str != '%' && *str != '}' && *str != 0) {
		if (*str == '\\') {
			switch (*(++str)) {
			case '\\':
				ft->param[i++] = '\\';
				break;
			case 't':
				ft->param[i++] = '\t';
				break;
			case 'n':
				ft->param[i++] = '\n';
				break;
			case 'r':
				ft->param[i++] = '\r';
				break;
			case 0:
				fprintf(stderr,
						"Invalid output format, wrong altered character.\n");
				awl_free(ft);
				return NULL;
				break;
			default:
				ft->param[i++] = *str;
				break;
			}
		} else {
			ft->param[i++] = *str;
		}
		if (i == MAX_OUTPUT_PARAM_LEN - 1) {
			ft->param[i] = 0;
			push(out_stream, ft);
			ft = (FormatToken *) awl_alloc(sizeof(FormatToken));
			ft->place_holder = VAR_CONST_STR;
			i = 0;
		}
		str++;
	}

	if (i > 0) {
		ft->param[i] = 0;
		push(out_stream, ft);
	} else {
		awl_free(ft);
	}

	return str;
}

static const char * output_parse_format(const char *str, list *out_stream) {
//	printf("str: %s\n", str);
	while (str != NULL && *str != 0) {
//		printf("str-loop: %s\n", str);
		switch (*str) {
		case '}':
			return str;
		case '%':
			str = _parse_place_holder(++str, out_stream);
			break;
		default:
			str = _parse_const_str(str, out_stream);
			break;
		}
	}
	if (str == NULL) {
		fprintf(stderr,
				"Invalid output format, please check your parameters.\n");
	}

	return str;
}

static inline void output_csv(FILE *fp, const char *str) {
	while (str != NULL && *str != 0) {
		switch (*str) {
		case '"':
			fputs("\"\"", fp);
			break;
		case '\'':
			fputs("''", fp);
			break;
		default:
			fputc(*str, fp);
			break;
		}
		str++;
	}
}

static inline void output_c(FILE *fp, const char *str) {
	while (str != NULL && *str != 0) {
		switch (*str) {
		case '"':
			fputs("\\\"", fp);
			break;
		case '\t':
			fputs("\\t", fp);
			break;
		case '\n':
			fputs("\\n", fp);
			break;
		case '\r':
			fputs("\\r", fp);
			break;
		case '\\':
			fputs("\\\\", fp);
			break;
			/**
			 *  those following characters may cause command line issue,
			 *  leading reverse slash is added to prevent potential trouble.
			 */
		case ' ':
			fputs("\\ ", fp);
			break;
		case ',':
			fputs("\\,", fp);
			break;
		case '|':
			fputs("\\|", fp);
			break;
		case '&':
			fputs("\\&", fp);
			break;
		case '$':
			fputs("\\$", fp);
			break;
		case '`':
			fputs("\\`", fp);
			break;
		default:
			fputc(*str, fp);
			break;
		}
		str++;
	}
}

static inline void output_str(FILE *fp, const char *str, AnalyzerParam *param) {
	switch (param->quote) {
	case QUOTE_CSV:
		output_csv(fp, str);
		break;
	case QUOTE_C:
		output_c(fp, str);
		break;
	case QUOTE_NONE:
	default:
		fputs(str, fp);
		break;
	}
}

static void output_print(FILE *fp, HttpLogInfo *logInfo, AnalyzerParam *param) {
	FormatToken *ft;
	char *value = NULL;

	move_head(param->format);
	while ((ft = next(param->format)) != NULL) {
		switch (ft->place_holder) {
		case VAR_HOST_ADDR:
			fprintf(fp, "%u.%u.%u.%u", logInfo->logEntry.ip.parts.a,
					logInfo->logEntry.ip.parts.b, logInfo->logEntry.ip.parts.c,
					logInfo->logEntry.ip.parts.d);
			break;
		case VAR_IDENTITY:
			fputs(logInfo->logEntry.identity, fp);
			break;
		case VAR_USERID:
			fputs(logInfo->logEntry.userid, fp);
			break;
		case VAR_TIME:
			fprintf(fp, "%04u-%02u-%02u %02u:%02u:%02u",
					logInfo->logEntry.time.year,
					logInfo->logEntry.time.month + 1,
					logInfo->logEntry.time.date, logInfo->logEntry.time.hour,
					logInfo->logEntry.time.minute,
					logInfo->logEntry.time.second);
			break;
		case VAR_TIME_YYYY:
			fprintf(fp, "%04u", logInfo->logEntry.time.year);
			break;
		case VAR_TIME_MM:
			fprintf(fp, "%02u", logInfo->logEntry.time.month + 1);
			break;
		case VAR_TIME_DD:
			fprintf(fp, "%02u", logInfo->logEntry.time.date);
			break;
		case VAR_TIME_HH:
			fprintf(fp, "%02u", logInfo->logEntry.time.hour);
			break;
		case VAR_TIME_II:
			fprintf(fp, "%02u", logInfo->logEntry.time.minute);
			break;
		case VAR_TIME_SS:
			fprintf(fp, "%02u", logInfo->logEntry.time.second);
			break;
		case VAR_TIME_ZONE:
			fprintf(fp, "%c%02u%02u",
					logInfo->logEntry.time.tz_flag == TZ_FLAG_WEST ? '-' : '+',
					logInfo->logEntry.time.tz_hour,
					logInfo->logEntry.time.tz_minute);
			break;
		case VAR_TIME_ZONE_FLAG:
			fprintf(fp, "%04u", logInfo->logEntry.time.year);
			break;
		case VAR_TIME_ZONE_HH:
			fprintf(fp, "%04u", logInfo->logEntry.time.year);
			break;
		case VAR_TIME_ZONE_II:
			fprintf(fp, "%04u", logInfo->logEntry.time.year);
			break;
		case VAR_REQUEST_METHOD:
			fputs(logInfo->logEntry.method, fp);
			break;
		case VAR_REQUEST_URL:
			output_str(fp, logInfo->logEntry.url, param);
			break;
		case VAR_REQUEST_PATH:
			output_str(fp, logInfo->logEntry.uri.path, param);
			break;
		case VAR_REQUEST_PARAM:
			value = NULL;
			if (logInfo->logEntry.uri.params != NULL) {
				value = (char *) get(logInfo->logEntry.uri.params, ft->param);
			}
			output_str(fp, value == NULL ? "" : value, param);
			break;
		case VAR_REQUEST_ANCHOR:
			output_str(fp, logInfo->logEntry.uri.anchor, param);
			break;
		case VAR_REQUEST_PROTOCOL:
			output_str(fp, logInfo->logEntry.protocol, param);
			break;
		case VAR_STATUS:
			fprintf(fp, "%u", logInfo->logEntry.status);
			break;
		case VAR_BYTES:
			fprintf(fp, "%u", logInfo->logEntry.bytes);
			break;
		case VAR_REFERER:
			output_str(fp, logInfo->logEntry.referer, param);
			break;
		case VAR_USER_AGENT:
			output_str(fp, logInfo->logEntry.user_agent, param);
			break;
		case VAR_CONST_STR:
			fputs(ft->param, fp);
			break;
		case VAR_FUNCTION:
			// TOOD
			break;
		default:
			// ?
			break;
		}
	}

	fputc('\n', fp);
}

#endif /* _OUTPUT_H_ */
